package com.jiang.thread.pool;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author jiangzhe
 * @create 2022/5/7 15:20
 */
@Slf4j
public class ThreadPool {

    //----------newFixedThreadPool-----------
    /**
     * 适用于任务量已知，相对耗时的任务
     * 1.submit(task) 此时还未工作
     * 2.future.get() 开启线程执行任务，返回结果
     */
    @Test
    public void testFixedThreadPool() throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Callable task = (Callable<String>) () -> {
//            Thread.sleep(3000);   // 超过 timeout 的时长会报 TimeoutException 异常
            Thread.sleep(1000);
            return "test";
        };
        log.info("main start...");
        Future<String> res = executorService.submit(task);
        log.info("task start...");
        log.info(res.get(2000, TimeUnit.MILLISECONDS));
    }
    /**
     * 多任务、带返回值
     * 1.submit(task)  任务要全部同时提交，不然会依次执行任务
     * 2.future.get()  总时长与核心线程数量、任务数量、任务执行时长有关
     */
    @Test
    public void testFixedThreadPoolMulTask() throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        List<Callable<String>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int finalI = i + 1;
            Callable<String> task = () -> {
                log.info(Thread.currentThread().getName() + " start --- task" + finalI);
                Thread.sleep(finalI * 1000);
                return "test" + finalI;
            };
            tasks.add(task);
        }
        List<Future<String>> futures = new ArrayList<>();
        for (Callable<String> task: tasks) {
            futures.add(executorService.submit(task));
        }
        for (Future<String> future: futures) {
            log.info(future.get());
        }
        executorService.shutdown();
    }
    /**
     * 多任务、带返回值
     * invokeAll(tasks)  在调 future.get() 前会处理完所有任务，这里需要等待的是最晚完成任务的时间
     *
     */
    @Test
    public void testFixedThreadPoolInvokeAll() throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        List<Callable<String>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int finalI = i + 1;
            Callable<String> task = () -> {
                log.info(Thread.currentThread().getName() + " start --- task" + finalI);
                Thread.sleep(finalI * 1000);
                return "test" + finalI;
            };
            tasks.add(task);
        }
        List<Future<String>> futures = executorService.invokeAll(tasks);
        for (Future<String> future: futures) {
            log.info(future.get());
        }
        executorService.shutdown();
    }

    //----------newCachedThreadPool-----------
    /**
     * 整个线程池表现为线程数会根据任务量不断增长，没有上限，当任务执行完毕，空闲 1分钟后释放线程。 适合任务数比较密集，但每个任务执行时间较短的情况
     */
    @Test
    public void testCacheThreadPool() throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Callable task = (Callable<String>) () -> {
            Thread.sleep(1000);
            return "test";
        };
        log.info("start...");
        Future<String> res = executorService.submit(task);
        log.info(res.get(2000, TimeUnit.MILLISECONDS));
    }
    /**
     * 多任务、带返回值
     * 总时长与单个任务执行最长时长有关，与任务数量无关（不考虑cpu核数情况下）
     */
    @Test
    public void testCachedThreadPoolMulTask() throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Callable<String>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int finalI = i + 1;
            Callable<String> task = () -> {
                log.info(Thread.currentThread().getName() + " start --- task" + finalI);
                Thread.sleep(finalI * 1000);
                return "test" + finalI;
            };
            tasks.add(task);
        }
        List<Future<String>> futures = new ArrayList<>();
        for (Callable<String> task: tasks) {
            futures.add(executorService.submit(task));
        }
        log.info("submit end");
        for (Future<String> future: futures) {
            log.info(future.get());
        }
        executorService.shutdown();
    }

    //----------newSingleThreadExecutor-----------
    /**
     * 希望多个任务排队执行。线程数固定为 1，任务数多于 1 时，会放入无界队列排队。任务执行完毕，这唯一的线程也不会被释放。
     */
    @Test
    public void testSingleThreadExecutor() throws InterruptedException, ExecutionException, TimeoutException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable task = (Callable<String>) () -> {
            Thread.sleep(1000);
            return "test";
        };
        log.info("start...");
        Future<String> res = executorService.submit(task);
        log.info(res.get(2000, TimeUnit.MILLISECONDS));
    }
    /**
     * 多任务、带返回值
     * 总时长与任务数量、任务执行时长有关，等于所有任务的执行时间总和
     */
    @Test
    public void testSingleThreadExecutorMulTask() throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        List<Callable<String>> tasks = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            int finalI = i + 1;
            Callable<String> task = () -> {
                log.info(Thread.currentThread().getName() + " start --- task" + finalI);
                Thread.sleep(finalI * 1000);
                return "test" + finalI;
            };
            tasks.add(task);
        }
        Instant start = Instant.now();
        List<Future<String>> futures = new ArrayList<>();
        for (Callable<String> task: tasks) {
            futures.add(executorService.submit(task));
        }
        log.info("submit end");
        for (Future<String> future: futures) {
            log.info(future.get());
        }
        log.info("used time: {}ms", Duration.between(start, Instant.now()).toMillis());
        executorService.shutdown();
    }
}
